#include "OBJ.hpp"

#include <GL/gl.h>

#include <fstream>
#include <sstream>
#include <iostream>
#include <stdexcept>
#include "StelCore.hpp"
#include "StelPainter.hpp"

using namespace std;

OBJ::OBJ( void )
    :loaded(false), vertices(), normals(), texcoords(), models(), stelModels()
{
}

OBJ::~OBJ( void )
{
    for (StelModelList::iterator iter = stelModels.begin(); iter != stelModels.end(); ++iter) {
        StelModel& stelModel = *iter;
        if (stelModel.vertice != NULL) {
            delete[] stelModel.vertice;
            stelModel.vertice = NULL;
        }
        if (stelModel.normalArray != NULL) {
            delete[] stelModel.normalArray;
            stelModel.normalArray = NULL;
        }
        if (stelModel.texCoords != NULL) {
            delete[] stelModel.texCoords;
            stelModel.texCoords = NULL;
        }
    }
}

vector<string> OBJ::splitStr(const string& line, char delim)
{
    int cutAt;
    string str(line);
    vector<string> results;
    while ((cutAt = str.find_first_of(delim)) != str.npos)
    {
        if (cutAt > 0)
        {
            results.push_back(str.substr(0, cutAt));
        }
        str = str.substr(cutAt + 1);
    }
    if (str.length() > 0)
    {
        results.push_back(str);
    }
    return results;
}

float OBJ::parseFloat(const std::string& str)
{
    stringstream ss(str);
    float f = 0;
    ss >> f;
    if (ss.fail()) {
        throw (str + " is not a number");
    }
    return f;
}

unsigned int OBJ::parseInt(const std::string& str)
{
    stringstream ss(str);
    unsigned int i = 0;
    ss >> i;
    if (ss.fail()) {
        throw (str + " is not a number");
    }
    return i;
}

void OBJ::load( const char* filename )
{
    ifstream file(filename);
    string line;
    if (file.is_open()) {
        Model model;
        while (!file.eof()) {
            getline(file, line);
            vector<string> parts = splitStr(line, ' ');
            if (parts.size() > 0) {
                if (parts[0] == "v") { // vertex (x,y,z)
                    if (parts.size() >= 4) {
                        Vertex v;
                        v.x = parseFloat(parts[1]);
                        v.y = parseFloat(parts[2]);
                        v.z = parseFloat(parts[3]);
                        vertices.push_back(v);
                    }
                } else if (parts[0] == "vt") { // texture (u,v)
                    if (parts.size() >= 3) {
                        Texcoord vt;
                        vt.u = parseFloat(parts[1]);
                        vt.v = parseFloat(parts[2]);
                        texcoords.push_back(vt);
                    }
                } else if (parts[0] == "vn") { // normal (x,y,z)
                    if (parts.size() >= 4) {
                        Vertex vn;
                        vn.x = parseFloat(parts[1]);
                        vn.y = parseFloat(parts[2]);
                        vn.z = parseFloat(parts[3]);
                        normals.push_back(vn);
                    }
                } else if (parts[0] == "g") { // polygon group
                    if (parts.size() > 1) {
                        if (model.faces.size() > 0) {
                            models.push_back(model);
                            model = Model();
                        }
                        model.name = parts[1];
                    }
                } else if (parts[0] == "usemtl") { // use material
                    if (parts.size() > 1) {
                        model.material = parts[1];
                    }
                } else if (parts[0] == "f") { // face
                    Face face;
                    for (unsigned int i=1; i<parts.size(); ++i) {
                        Ref ref;
                        vector<string> f = splitStr(parts[i], '/');
                        if (f.size() >= 2) {
                            ref.v = parseInt(f[0]) - 1;
                            ref.texture = false;
                            if (f[1].size() > 0) {
                                ref.t = parseInt(f[1]) - 1;
                                ref.texture = true;
                            }
                            ref.normal = false;
                            if (f.size() > 2) {
                                ref.n = parseInt(f[2]) - 1;
                                ref.normal = true;
                            }
                        }
                        face.refs.push_back(ref);
                        model.refCount++;
                    }
                    if (face.refs.size() >= 3) {
                        model.faces.push_back(face);
                    }
                }
            }
        }
        if (model.faces.size() > 0) {
            models.push_back(model);
            model = Model();
        }
        loaded = true;
        cout << vertices.size() << " vertices." << endl;
        cout << normals.size() << " normals." << endl;
        cout << texcoords.size() << " texture coordinates." << endl;
        cout << models.size() << " models." << endl;
        cout << filename << " loaded." << endl;
    } else {
        cerr << "Couldn't open " << filename << endl;
    }
}

void OBJ::prepareStelModels( void )
{
    for (ModelList::const_iterator iter = models.begin(); iter != models.end(); ++iter) {
        const Model& model = *iter;
        StelModel stelModel;
        stelModel.size = model.refCount;
        stelModel.vertice = new Vec3d[model.refCount];
        stelModel.normalArray = new Vec3f[model.refCount];
        stelModel.texCoords = NULL;
        unsigned int i = 0;
        for (FaceList::const_iterator iter = model.faces.begin(); iter != model.faces.end(); ++iter) {
            const Face& face = *iter;
            for (RefList::const_iterator iter = face.refs.begin(); iter != face.refs.end(); ++iter) {
                if (i >= model.refCount) {
                    stringstream ss;
                    ss << "model.refCount(" << model.refCount << ") incorrect";
                    throw runtime_error(ss.str());
                }
                const Ref& ref = *iter;
                const Vertex& v = vertices[ref.v];
                stelModel.vertice[i] = Vec3d(v.x, v.y, v.z);
                if (ref.normal) {
                    const Vertex& n = normals[ref.n];
                    stelModel.normalArray[i] = Vec3f(n.x, n.y, n.z);
                }
                /*
                if (ref.texture) {
                    const Texcoord& t = texcoords[ref.t];
                    stelModel.texCoords[i] = Vec2f(t.u, t.v);
                }
                */
                i++;
            }
        }
        stelModels.push_back(stelModel);
    }
}

void OBJ::draw( StelCore* core )
{
    StelPainter* painter = new StelPainter(core->getProjection(StelCore::FrameAltAz, StelCore::ProjectionPerspective));
    painter->setColor(1.f, 1.f, 1.f);
    for (StelModelList::const_iterator iter = stelModels.begin(); iter != stelModels.end(); ++iter) {
        const StelModel& stelModel = *iter;
        painter->setArrays(stelModel.vertice, /*stelModel.texCoords*/NULL, NULL, stelModel.normalArray);
        painter->drawFromArray(StelPainter::Triangles, stelModel.size, 0, true, NULL);
    }
}

